package com.cww.study.utils;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

/**
 * @program: sbd
 * @description:
 * @author: 陈英俊
 * @create: 2022-01-24 09:32
 */
@Component
public class RedisUtils {
    //private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);


    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 限制方法重复消费
     *
     * @param md5        参数md5
     * @param userName   用户名
     * @param methodName 方法名
     * @param expireTime 存活时间 单位毫秒
     * @return
     */
    public Boolean limitMethodRepeatedConsumption(String md5, String userName, String methodName, long expireTime) {


        // 将项目名称,操作人姓名,方法名,方法参数拼接作为key
        String redisKey = "sbd:U=" + userName + "M=" + methodName + "P=" + md5;

        //expireTime时间内的重复请求会认为重复
        long expireAt = System.currentTimeMillis() + expireTime;

        //直接SETNX不支持带过期时间，所以设置+过期不是原子操作，极端情况下可能设置了就不过期了或者死锁，后面相同请求可能会误以为需要去重,所以这里使用底层API，保证SETNX+过期时间是原子操作
        String longTimeStr = "expireAt@" + expireAt;

        /**
         * 1采用原生的api,2采用封装的方法,2个方法就是一个不一样,
         * 1 调用的是 execute(RedisCallback<T> action)
         * 2 调用的是  execute(RedisCallback<T> callback, boolean exposeConnection)
         * 少的这个参数:exposeConnection　是干嘛的呢？
         * exposeConnection参数的含义是是否要暴露connection，如果为true，那么就可以在回调函数中使用当前连接connection对象
         * 百度的结果,其实我没搞明白,GG
         */
        // 1
        Boolean setResult = stringRedisTemplate.execute(
                (RedisCallback<Boolean>) connection -> connection.set(
                        redisKey.getBytes(),// 键的字节数组
                        longTimeStr.getBytes(),//时间的字节数组
                        Expiration.milliseconds(expireTime),//构建毫秒
                        RedisStringCommands.SetOption.SET_IF_ABSENT)//setNX
        );
        // 2
        //stringRedisTemplate.opsForValue().setIfAbsent(redisKey, longTimeStr, 20, TimeUnit.SECONDS);


        // execute参数是RedisCallback,其实传入的实现了RedisCallback接口的匿名子类,最终是匿名子类执行doInRedis();
//        stringRedisTemplate.execute(new RedisCallback<Boolean>() {
//            @Override
//            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
//                return connection.set(redisKey.getBytes(), longTimeStr.getBytes(), Expiration.milliseconds(expireTime), RedisStringCommands.SetOption.SET_IF_ABSENT);
//            }
//        });
        final boolean result;
        result = setResult == null || !setResult;
        return result;

    }
}
